البرمجة المتفرقة

بسم الله الرحمن الرحيم

بعد ظهور لغات البرمجة القريبة من لغة الإنسان. ظهرت الحاجة إلى إيجاد وسيلة تسمح بـ:

هذه الوسيلة المبتكرة هي ملفات الكائنات ( Object Files ).

الفكرة بسيطة من وراء استعمال ملفات الكائنات وهي:

  1.  يكتب ملف مصدر لجزء من برنامج بلغة برمجة سواء كانت أسمبلر أو لغة ذات مستوى أعلى ( C , Pascal , Ada ).
  2. يقوم المجمع ( Compiler ) بتحليل ملف المصدر ثم تركيب و صناعة ملف كائنات موافق.
  3. تجمع جميع ملفات الكائنات الخاصة بهذا البرنامج ثم تربط فيما بينها بواسطة برنامج يسمى برنامج الربط ( linker ) لينتج ملف واحد هو برنامج كامل مكتوب بلغة الآلة ( 0 و 1 ) قابل للتنفيذ. يسمى الملف التنفيذي للبرنامج (Executable File) .
يمكن أيضا استعمال برنامج الربط لصناعة مكتبات برامجية تحوي عدة ملفات كائنات و هذا بهدف إعادة استعمالها في برامج أخرى. هذه المكتبات تنقسم إلى نوعين:

مكونات ملف الكائنات :

يحتوي ملف الكائنات على معلومات مختلفة تساعد برنامج الربط على أداء عمله بنجاح. من بينها:

الرموز المذكورة سابقا يمكن أن تكون أسماء متغيرات (Variables)، أسماء دوال(Functions) و إجراءات(Procedures) أو غيرها مثل البطاقات ( Labels ).

مكونات ملف التنفيذي للبرنامج (Executable File) :

يمكن اعتبار الملف التنفيذي كملف كائنات مخفف لأنه لا يحتوي إلا على بعض مكونات ملف الكائنات العادي.

الأمثلة التطبيقية:

يمكننا الآن تعلم كيفية استعمال برنامج تجميع للأسمبلر + برنامج تجميع للغة C + برنامج ربط = لصناعة برنامج تنفيذي بسيط لا يحتوي إلا على تعليمات الآلة + القيم الابتدائية و الثابتة للمتغيرات.
سنحاول في الأمثلة التركيز على آلتين هما المعالج 8086 و المعالج 386.

البرامج المستعملة هي:

اسم الآلة 8086 386
نوع البرنامج اسم البرنامج رقم النسخة اسم البرنامج رقم النسخة
برنامج تجميع لغة C bcc 0.16.3 gcc 0.98.35
برنامج تجميع لغة الأسمبلر as86 0.16.3 nasm 3.3.1
برنامج الربط ld86 0.16.3 ld 2.14.90.5

رقم نسخة هذه البرامج غير مهم إلا في حالة عدم نجاح العملية التطبيقية. في هذه الحالة يرجى استعمال نسخة مماثلة أو أكبر . و الرجوع إلى ملفات المساعدة المرفقة بها.

الخطوات:
في كلا الآلتين 8086 و 386 سنحاول كتابة برنامج بلغة أسمبلر ينادي على دالة بلغة C لحساب المجموع . يمكن التعبير عن برنامج الأسمبلر الذي سيكتب على شكل التعليمة التالية بلغة C:

sum(15,10)

حيث أن "sum" هو اسم دالة الجمع. العدد 15 هو العامل الأول للدالة "sum" و العدد 10 هو العامل الثاني.

الآلة 8086 :

هذه الآلة تستعمل سجلات ذات حجم 16 bit مثل DX,CX,BX,AX لذا يدعى البرنامج الذي ينفذ عليها ببرنامج 16 bit.
نستعمل البرامج  ld86 , bcc , as86 التي تنتج هذا النوع من البرامج (16 bit).

الملف "f1.s" :

entry _start تعريف البطاقة start_ على أنها رمز البداية لتعليمات الملف.
_start: التصريح بالبطاقة start_ .
          mov ax,#10
          push ax
وضع العامل الثاني في السجل ax ثم تكويمه في الكومة.
          mov ax,#15
          push ax
وضع العامل الأول في السجل ax ثم تكويمه في الكومة.
          call _sum مناداة دالة المجموع sum.
          add sp,#4 استرجاع مكان تخزين العوامل في الكومة. ( 2 + 2 = 4 Byte مكان التخزين)
          ret نهاية البرنامج الصغير.
          nop أضيفت للحصول على 16 Byte. و هو اصغر حجم لقسم (Segment).

أظن أنك لاحظت وجود الرمز "sum_" و ليس الرمز "sum" الذي يرمز للدالة المكتوبة بلغة C و المسماة "(int sum(int a,int b". هذا راجع إلى أن أغلب مجمعات اللغة C تحول اسم أي رمز بإضافة الحرف '_' في أوله لتلافي خطأ و جود اسم مشابه له في ملف مكتوب بلغة الأسمبلر.

هنالك أيضا التوجيه "entry _start" هو يهدف إلى الحصول على رمز بداية للملف . لكن هذا خاص فقط بالبرنامج bcc. عندما تنجح في الخطوات التالية جرب حذف هذا التوجيه من الملف و أعد الخطوات و ستجد أنك لا تنجح في العملية.

لصناعة الملف "f1.o" نكتب في سطر الأوامر ما يلي:

as86 -u -o f1.o f1.s

يمكنك التأكد من وجود الملف "f1.o" بالأمر "ls".

التوجيه "u-" يأمر البرنامج "as86" باعتبار أي رمز يستعمل داخل الملف "f1.s" و لم يصرح به داخله على أنه رمز خارجي. الرمز "sum_" هو رمز مستعمل و لكنه لم يصرح به. إذن فسيعتبره "as86" رمز خارجي.

التوجيه "o-" يحدد اسم الملف المنتج. في حالتنا اسمه "f1.o".

الملف "f2.c" :
نكتب الملف "f2.c" و هو يحتوي على دالة اسمها "sum" تقوم بجمع عددين صحيحين ثم تعيد نتيجة المجموع إلى المنادي.

int sum(int a,int b)
{
        return a+b;
}

لصناعة الملف "f2.o" نكتب في سطر الأوامر ما يلي:

bcc -ansi -c f2.c

التوجيه "ansi-" يأمر "bcc" بالتقيد بلغة C المقررة من طرف المنظمة الأمريكية ANSI.

التوجيه "c-" يأمر "bcc" بصناعة ملف الكائنات "f2.o" فقط .

الملف "f3.bin" :
لربط الملفين المنتجين سابقا نستعمل البرنامج "ld86" للحصول على الملف "f3.bin". نكتب في سطر الأوامر ما يلي :

ld86 -d -o f3.bin f1.o f2.o

من المهم جدا ترتيب ملفات الكائنات عند تنفيذ برنامج الربط. قمنا بربط الملف "f1.o" ثم الملف "f2.o".

التوجيه "d-" أمر "ld86" بصناعة ملف خالي من أي جداول رموز أو غيرها. بل يحتوي فقط على التعليمات و البيانات الموجودة في الملفين "f1.o" و "f2.o".

رؤية النتيجة:

الآن حصلنا على الملف "f3.bin". لرؤية محتواه بلغة الأسمبلر نكتب ما يلي في سطر الأوامر:

ndisasm f3.bin

فنحصل على :

الانتقال

التعليمة بالأساس 16

التعليمة بالاسمبلر الوصف الملف
00000000
00000003
00000004
00000007
00000008
0000000B
0000000E
0000000F
B80A00
50
B80F00
50
E80500
83C404
C3
90
mov ax,0xa
push ax
mov ax,0xf
push ax
call 0x10
add sp,byte +0x4
ret
nop
الملف f1.s مكتوب بصيغة Nasm.

في التعليمة "
call 0x10" قام البرنامج "ld86" بتبديل الرمز "sum_" بالعدد 0x10 وهو العنوان الجديد للدالة "sum" في هذا الملف. لاحظ فقط السطر ذو الانتقال "00000010".
 
الملف f1.s
00000010
00000011
00000013
00000014
55
89E5
57
56
push bp
mov bp,sp
push di
push si
حفظ بعض السجلات. و التحضير لاستعمال السجل bp لاستخراج عوامل الدالة. الملف
f2.c
00000015 8B4604 mov ax,[bp+0x4] استرجاع العامل الأول من الكومة و وضعه داخل السجل ax.
00000018 034606 add ax,[bp+0x6] جمع العامل الأول مع الثاني
0000001B
0000001C
0000001D
5E
5F
5D
pop si
pop di
pop bp
إعادة القيم السابقة لبعض السجلات
0000001E C3 ret الرجوع إلى الإجراء المنادي
0000001F 00 db 0x00 هذا الByte أضيف لملء الفراغ فقط.

الآلة 386 :

هذه الآلة تستعمل سجلات ذات حجم 32 bit مثل EDX,ECX,EBX,EAX لذا يدعى البرنامج الذي ينفذ عليها ببرنامج 32 bit.
نستعمل البرامج  ld , gcc , nasm التي تنتج هذا النوع من البرامج (32 bit).

الملف "f1.s" :
هذا البرنامج مكتوب بصيغة "nasm". هي تختلف كثيرا عن صيغة "as86".

[bits 32] التصريح بأن نوع البرنامج هو 32 bit.
extern sum التصريح بأن الرمز sum هو رمز خارجي مستورد.
global _start تعريف البطاقة start_ على أنها رمز مورّد خارج الملف.
_start: التصريح بالبطاقة start_ .
         push 10 وضع العامل الثاني في الكومة مباشرة دون الحاجة إلى وسيط . هذه هي أحد ميزات الآلة 386
         push 15 وضع العامل الأول في ي الكومة مباشرة.
call sum مناداة دالة المجموع sum.
        add esp,8 استرجاع مكان تخزين العوامل في الكومة. ( 4 + 4 = 8 Byte مكان التخزين)
          ret نهاية البرنامج الصغير.

أظن أنك لاحظت استعمال الرمز "sum" مباشرة و ليس "sum_" هذا لأن هذه النسخة المستعملة من "gcc" و النسخ التي تليها تترك الرموز على حالها. لكن النسخ الأقدم كانت تحوله بإضافة الحرف '_' في أول الرمز.

لصناعة الملف "f1.o" نكتب في سطر الأوامر ما يلي:

nasm -f elf -o f1.o f1.s

التوجيه "f elf-" يأمر البرنامج "nasm" بإنتاج ملف كائنات من نوع ELF المستعمل أيضا من طرف البرنامج "gcc". من الأفضل الحصول على ملفات كائنات من نفس النوع لربطها معا. لكن يمكن استعمال ملفات من أنواع مختلفة.

التوجيه "o-" يحدد اسم الملف المنتج. في حالتنا اسمه "f1.o".

الملف "f2.c" :
نكتب الملف "f2.c" و هو نفس الملف الذي استعملناه مع 8086. هنا تظهر جليا فائدة لغات البرمجة المتطورة حيث أنها تسمح بإعادة استعمال نفس برنامج المصدر لصناعة برامج تنفذ على آلات مختلفة.

int sum(int a,int b)
{
        return a+b;
}

لصناعة الملف "f2.o" نكتب في سطر الأوامر ما يلي:

gcc -c f2.c

التوجيه "c-" يأمر "gcc" بصناعة ملف الكائنات "f2.o" فقط .

الملف "f3.bin" :
لربط الملفين المنتجين سابقا نستعمل البرنامج "ld" للحصول على الملف "f3.bin". نكتب في سطر الأوامر ما يلي :

ld --oformat binary -o f3.bin -Ttext 0x0 f1.o f2.o

من المهم جدا ترتيب ملفات الكائنات عند تنفيذ برنامج الربط. قمنا بربط الملف "f1.o" ثم الملف "f2.o".

التوجيه "oformat binary--" أمر "ld" بصناعة ملف خالي من أي جداول رموز أو غيرها. بل يحتوي فقط على التعليمات و البيانات الموجودة في الملفين "f1.o" و "f2.o".

التوجيه "Ttext-" يأمر "ld" بانتاج برنامج تبدأ تعليماته من العنوان 0x0.

رؤية النتيجة:

الآن حصلنا على الملف "f3.bin". لرؤية محتواه بلغة الأسمبلر نكتب ما يلي في سطر الأوامر:

ndisasm -u f3.bin

التوجيه "u-" يأمر "ndisasm" بتحليل البرنامج إلى تعليمات خاصة بـ 32 bit.

نحصل على :

الانتقال

التعليمة بالأساس 16

التعليمة بالاسمبلر الوصف الملف
00000000
00000005
0000000A
0000000F
00000015
00000016
00000017
680A000000
680F000000
E809000000
81C408000000
C3
90
90
push dword 0xa
push dword 0xf
call 0x18
add esp,0x8
ret
nop
nop
الملف f1.s مكتوب بصيغة Nasm.

في التعليمة "
call 0x18" قام البرنامج "ld" بتبديل الرمز "sum" بالعدد 0x18 وهو العنوان الجديد للدالة "sum" في هذا الملف. لاحظ فقط السطر ذو الانتقال "00000018".

استعملت التعليمة nop لملأ الفراغ.و الحصول على عنوان للملف f2.o من مضاعفات العدد 4.
الملف f1.s
00000018
00000019
55
89E5
push ebp
mov ebp,esp
التحضير لاستعمال السجل ebp لاستخراج عوامل الدالة. الملف
f2.c
0000001B 8B450C mov eax,[ebp+0xc] استرجاع العامل الثاني من الكومة و وضعه داخل السجل eax.
0000001E 034508 add eax,[ebp+0x8] جمع العامل الأول مع الثاني.
00000021 5D pop ebp إعادة القيم السابقة للسجل epb.
00000022 C3 ret الرجوع إلى الإجراء المنادي.

من يريد المزيد:

يمكنك الآن التوسع في الموضوع و تطوير برامج بواسطة لغات برمجة مختلفة. و الاستفادة من برامجك السابقة. عند محاولة فعل ذلك ينبغي عليك دراسة التوجيهات المستعملة من قبل برامج الربط و مجمعات اللغات المراد استخدامها.

الكاتب : مراد حمود

التاريخ : 15/12/2004